﻿#region usings
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using InsuranceAdjuster.Helpers;
using InsuranceAdjuster.OccasionallyConnected.CacheMonitors;
using InsuranceAdjuster.OccasionallyConnected.Messages;
using InsuranceAdjuster.OccasionallyConnected.ObjectStores;
using System.IO.IsolatedStorage;
#endregion

namespace InsuranceAdjuster.OccasionallyConnected {
    public partial class EntityManager : INotifyPropertyChanged {
        #region Initialization
        public delegate void EntityChangedEventHandler(Guid entityGuid, Type entityType, ChangeOperation changeOperation, Origin changeOrigin, IEntity entity);
        public event EntityChangedEventHandler EntityChanged;

        public delegate void MessageCompletedEventHandler(Message message);
        public event MessageCompletedEventHandler MessageCompleted;

        public delegate void NetworkAvailabilityChangedEventHandler(Object sender, NetworkAvailabilityEventArgs e);
        public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
        public delegate void WorkOfflineChangedEventHandler(object sender, bool workOffline);
        public event WorkOfflineChangedEventHandler WorkOfflineChanged;

        private EntityManager() {
            // Initalize the Object Store

            _objectStore = new SterlingObjectStore();

            _objectStore.PropertyChanged += (sender, e) => {
                switch (e.PropertyName) {
                    case "Count":
                        RaisePropertyChanged("EntitiesInQueue");
                        break;
                }
            };

            // Maintain the cache (sync)
            _cacheMonitor = new PollingCacheMonitor();
            _cacheMonitor.Initialize(this);
            _cacheMonitor.Start();

            // Monitor Network Changes (Online/Offline)
            NetworkChange.NetworkAddressChanged += NetworkAddressChanged;

            ThreadPool.QueueUserWorkItem(new WaitCallback(o => {
                foreach (var message in _objectStore.GetDurableMessages()) {
                    ProcessMessage(message);
                }
            }));
        }
        #endregion

        public void ProcessMessage(Message message) {
            ProcessMessage(message, false);
        }
        public void ProcessMessage(Message message, bool isSyncMessage) {
            lock (_messages) {
                _messages.Add(message);

                // Attempt to save
                // if message.IsDurable, it will be saved, else it will just return
                // Allows easy switching of logic based on type of ObjectStore
                GetObjectStore().UpdateDurableMessage(message);

                message.PropertyChanged += Message_PropertyChanged;
                RaisePropertyChanged("Messages");

                Action<List<MessageResult>> Callback = null;
                if (isSyncMessage)
                    Callback = resultingActions => ThreadPool.QueueUserWorkItem(data => ProcessMessageResults(resultingActions));
                else
                    Callback = resultingActions => ProcessMessageResults(resultingActions);

                Message.ExecuteMessage(message, resultingActions => ProcessMessageResults(resultingActions),
                    () =>
                        // MessageCompleteHandler
                    {
                        OnMessageCompleted(message);
                        message.Archive();
                    });
            }
        }

        private void ProcessMessageResults(List<MessageResult> messageResults) {
            foreach (var Item in messageResults) {
                if (Item.EntityGuid == Guid.Empty)
                    continue;

                switch (Item.Verb) {
                    case Verb.Read:
                        _objectStore.SetEntity(Item.Entity);

                        if (Item.BroadcastResult)
                            OnEntityChanged(ChangeOperation.Read, Item.Origin, Item.Entity);
                        break;

                    case Verb.Update:
                        _objectStore.SetEntity(Item.Entity);

                        if (Item.BroadcastResult)
                            OnEntityChanged(ChangeOperation.Update, Item.Origin, Item.Entity);
                        break;

                    case Verb.Delete:
                        _objectStore.RemoveEntity(Item.EntityGuid);
                        System.Diagnostics.Debug.WriteLine("Delete" + Item.ToString());
                        if (Item.BroadcastResult)
                            OnEntityChanged(ChangeOperation.Delete, Item.Origin, Item.Entity);
                        break;

                    case Verb.Create:
                        _objectStore.SetEntity(Item.Entity);

                        if (Item.BroadcastResult)
                            OnEntityChanged(ChangeOperation.Create, Item.Origin, Item.Entity);
                        break;

                }
            }
        }

        private static EntityManager _current;
        public static EntityManager Current {
            get {
                if (null == _current) {
                    _current = new EntityManager();
                }
                return _current;
            }
        }

        private ObservableCollection<Message> _messages = new ObservableCollection<Message>();
        public ObservableCollection<Message> Messages {
            get { return _messages; }
        }

        #region Online/Offline Detection

        public bool WorkOffline {
            get { return (bool)IsolatedStorageSettings.ApplicationSettings["WorkOffline"]; }
            set {
                if (WorkOffline != value) {
                    IsolatedStorageSettings.ApplicationSettings["WorkOffline"] = value;

                    // Fire the event
                    if (null != WorkOfflineChanged) {
                        Deployment.Current.Dispatcher.BeginInvoke(() =>
                            WorkOfflineChanged(this, WorkOffline));
                    }

                    if (null != NetworkAvailabilityChanged) {
                        Deployment.Current.Dispatcher.BeginInvoke(() =>
                            NetworkAvailabilityChanged(this, new NetworkAvailabilityEventArgs(this.IsOnline)));
                    }

                    RaisePropertyChanged("WorkOffline");
                    RaisePropertyChanged("IsOnline");
                }
            }
        }

        private void NetworkAddressChanged(object sender, EventArgs e) {
            if (null != NetworkAvailabilityChanged) {
                NetworkAvailabilityChanged.Invoke(this,
                    new NetworkAvailabilityEventArgs(NetworkInterface.GetIsNetworkAvailable()));
            }
        }

        private bool _isOnline = NetworkInterface.GetIsNetworkAvailable();
        /// <summary>
        /// Is the EntityManager connected 
        /// </summary>
        public bool IsOnline {
            get {
                if (this.WorkOffline) {
                    return false;
                }
                else {
                    return NetworkInterface.GetIsNetworkAvailable();
                }
            }
            private set {
                if (_isOnline != value) {
                    _isOnline = value;
                    // Fire the event
                    if (null != NetworkAvailabilityChanged) {
                        Deployment.Current.Dispatcher.BeginInvoke(() =>
                            NetworkAvailabilityChanged(this, new NetworkAvailabilityEventArgs(this.IsOnline)));
                    }
                    RaisePropertyChanged("IsOnline");
                }
            }
        }

        public void ToggleOnOfflineMode() {
            // Toggle the WorkOffline mode
            this.WorkOffline = !this.WorkOffline;
        }

        public void CheckNetwrokState() {
            // Use the private property set to raise a PropertyChanged & Event 
            // using the logic within the IsOnline property get
            this.IsOnline = this.IsOnline;
        }
        #endregion

        public int MessagesInFlight {
            get {
                lock (_messages) {
                    return _messages.Where(x => x.Status == MessageStatus.ProcessingCache || x.Status == MessageStatus.ProcessingServer).Count();
                }
            }
        }

        private IObjectStore _objectStore;
        public int EntitiesInQueue {
            get { return _objectStore.Count; }
        }

        public int MessagesPending {
            get {
                lock (_messages) {
                    return _messages.Where(x => x.Status == MessageStatus.InQueue || x.Status == MessageStatus.WaitingForNetworkConnection).Count();
                }
            }
        }

        private ICacheMonitor _cacheMonitor = null;
        public ICacheMonitor CacheMonitor {
            get { return _cacheMonitor; }
        }

        public IObjectStore GetObjectStore() {
            return _objectStore;
        }
         
        public bool CheckForRedundentMessages(Message message) {
            lock (_messages) {
                var Result = _messages.Where(x => x.Status == MessageStatus.WaitingForNetworkConnection
                                                    || x.Status == MessageStatus.ProcessingCache
                                                    || x.Status == MessageStatus.ProcessingServer)
                                      .Any(x => x.MessageIdentifier.Equals(message.MessageIdentifier));
                return Result;
            }
        }

        protected void OnEntityChanged(ChangeOperation changeOperation, Origin origin, IEntity entity) {
            OnEntityChanged(changeOperation, origin, entity.GetType(), entity.EntityGuid, entity);
        }
        protected void OnEntityChanged(ChangeOperation changeOperation, Origin origin, Guid entityGuid) {
            OnEntityChanged(changeOperation, origin, null, entityGuid, null);
        }
        protected void OnEntityChanged(ChangeOperation changeOperation, Origin origin, Type entityType, Guid entityGuid, IEntity entity) {
            if (EntityChanged == null)
                return;

            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                EntityChanged.Invoke(entityGuid, entityType, changeOperation, origin, entity);
            });
        }
        protected void OnMessageCompleted(Message message) {
            if (message.CallerGuid == null || message.CallerGuid == Guid.Empty)
                return;
            if (MessageCompleted == null)
                return;

            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                MessageCompleted.Invoke(message);
            });
        }

        private void Message_PropertyChanged(object sender, PropertyChangedEventArgs e) {
            switch (e.PropertyName) {
                case "Status":
                    RaisePropertyChanged("MessagesInFlight");
                    RaisePropertyChanged("MessagesPending");
                    break;
            }
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string propertyName) {
            if (null != PropertyChanged) {
                Deployment.Current.Dispatcher.BeginInvoke(() => PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)));
            }
        }
        #endregion
    }
}
